package com.vivo.ic.dm;

import android.content.Context;
import android.net.http.AndroidHttpClient;
import android.os.Handler;
import android.os.Message;
import android.os.Process;
import android.os.SystemClock;
import android.text.TextUtils;
import android.util.Pair;

import com.vivo.ic.VLog;
import com.vivo.ic.dm.DownloadInfo.NetworkState;

import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;

/**
 * @author wangzhenyu 子下载线程
 */

public class ChildDownloadThread extends Thread {
    private static final String TAG = Constants.PRE_TAG + "ChildDownloadThread";

    private Context mContext;
    private final DownloadInfo mInfo;
    private ChildDownloadInfo mChildInfo;
    private Handler mHandler;
    private InputStream mEntityStream;
    private static final long DEFAULT_SAMPLE_TIME = 600;

    public ChildDownloadThread(Context context, DownloadInfo info,
                               ChildDownloadInfo childInfo, Handler handler) {
        mInfo = info;
        mContext = context;
        mChildInfo = childInfo;
        mHandler = handler;
    }

    @Override
    public void run() {
        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
        AndroidHttpClient client = null;
        try {
            client = AndroidHttpClient.newInstance(mInfo.getUserAgent(), mContext);
            HttpGet request = new HttpGet(mChildInfo.mUrl);
            executeDownload(mChildInfo, client, request);
        } catch (StopRequestException e) {
            handleDownFailed(e.getFinalStatus(), e);
        } catch (Exception e) {
            handleDownFailed(Downloads.Impl.STATUS_UNKNOWN_ERROR, e);
        } finally {
            if (client != null) {
                client.close();
            }
            syncDataToMain(ChildDownloadManager.DOWNLOAD_FINISH_MESSAGE, mChildInfo);
            // 清除无效的临时文件
            if (Downloads.Impl.isStatusError(mInfo.mStatus)) {
                clearTmpFile(mChildInfo.mBlockName);
            }
            if (mEntityStream != null) {
                try {
                    logDebug(" mEntityStream.close()");
                    mEntityStream.close();
                } catch (IOException e) {
                    logError(" mEntityStream.close() exception: ", e);
                }
            }
            logDebug(" child thread is over, mStatus: " + mInfo.mStatus);
        }
    }

    private void executeDownload(ChildDownloadInfo childInfo, AndroidHttpClient client, HttpGet request)
            throws StopRequestException {
        logDebug(" executeDownload() childInfo: " + childInfo);
        byte[] data = new byte[Constants.BUFFER_SIZE];

        setupDestinationFile(childInfo);

        // skip when already finished
        if (childInfo.mCompleted || (childInfo.mCurrentBytes != 0 && childInfo.mCurrentBytes == childInfo.mTotalBytes)) {
            logDebug(" executeDownload, Skipping initiating request, already completed");
            return;
        }
        addRequestHeaders(childInfo, request);

        HttpResponse response = sendRequest(client, request);

        logDebug(" ChildDownloadThread executeDownload, received response for " + childInfo.mUrl);

        mEntityStream = openResponseEntity(response);
        transferData(childInfo, data, mEntityStream);
    }

    /**
     * Prepare the destination file to receive data. If the file already exists, we'll set up
     * appropriately for resumption.
     */
    private void setupDestinationFile(ChildDownloadInfo childInfo) throws StopRequestException {
        logDebug(" setupDestinationFile() mResume " + childInfo.mResume);
        if (!childInfo.mBlockName.isEmpty()) {
            File f = new File(childInfo.mBlockName);
            long fileLength = f.length();
            if (!childInfo.mResume) {
                logDebug(" it is not resume");
                if (f.exists()) {
                    logDebug(" setupDestinationFile(), not resume, found file, deleting ");
                    f.delete();
                }
                try {
                    childInfo.mOutStream = new FileOutputStream(childInfo.mBlockName);
                } catch (FileNotFoundException exc) {
                    throw new StopRequestException(Downloads.Impl.STATUS_FILE_ERROR, exc.toString(), exc);
                }

            } else { // 断点续传
                logDebug(" setupDestinationFile() it is resume");
                if (f.exists()) {

                    if (fileLength == 0) {
                        // The download hadn't actually started, we can restart from scratch
                        logDebug(" setupDestinationFile() resume found fileLength=0, deleting");
                        f.delete();
                    } else if (fileLength == childInfo.mTotalBytes) {
                        // 暂停前该子下载已经完成
                        logDebug(" setupDestinationFile() this child has completed before");
                        childInfo.mCompleted = true;
                    } else {
                        // All right, we'll be able to resume this download
                        logDebug(" setupDestinationFile(), resuming download, and block file length: " + fileLength);
                        try {
                            childInfo.mOutStream = new FileOutputStream(childInfo.mBlockName, true);
                        } catch (FileNotFoundException exc) {
                            throw new StopRequestException(Downloads.Impl.STATUS_FILE_ERROR,
                                    "while opening destination for resuming: " + exc.toString(), exc);
                        }
                    }
//                    childInfo.mCurrentBytes = fileLength;
                } else {
                    try {
                        childInfo.mOutStream = new FileOutputStream(childInfo.mBlockName, true);
                    } catch (FileNotFoundException exc) {
                        throw new StopRequestException(Downloads.Impl.STATUS_FILE_ERROR, exc.toString(), exc);
                    }
                }
            }

        }

        closeDestination(childInfo);
    }

    /**
     * Add custom headers for this download to the HTTP request.
     */
    private void addRequestHeaders(ChildDownloadInfo childInfo, HttpGet request) {
        for (Pair<String, String> header : mInfo.getHeaders()) {
            request.addHeader(header.first, header.second);
        }
        childInfo.mStartBytes += childInfo.mCurrentBytes;
        request.addHeader("Range", "bytes=" + childInfo.mStartBytes + "-" + childInfo.mEndBytes);
        logDebug(" addRequestHeaders() childInfo.mStartBytes = " + childInfo.mStartBytes + " childInfo.mEndBytes = "
                + childInfo.mEndBytes);
    }

    /**
     * Send the request to the server, handling any I/O exceptions.
     */
    private HttpResponse sendRequest(AndroidHttpClient client, HttpGet request)
            throws StopRequestException {
        try {
            return client.execute(request);
        } catch (IllegalArgumentException ex) {
            logError(" sendRequest ", ex);
            throw new StopRequestException(Downloads.Impl.STATUS_HTTP_DATA_ERROR,
                    "while trying to execute request: " + ex.toString(), ex);
        } catch (IOException ex) {
            logError(" sendRequest ", ex);
            throw new StopRequestException(getFinalStatusForHttpError(),
                    "while trying to execute request: " + ex.toString(), ex);
        }
    }

    private int getFinalStatusForHttpError() {
        logDebug(" getFinalStatusForHttpError()");
        final NetworkState networkUsable = mInfo.checkCanUseNetwork();
        if (networkUsable != NetworkState.OK) {
            if (!DownloadManager.getConfig().allowUsingMobileNet() && networkUsable == NetworkState.MOBILE) {
                return Downloads.Impl.STATUS_QUEUED_FOR_WIFI;
            }
            return Downloads.Impl.STATUS_WAITING_FOR_NETWORK;
        } else if (mInfo.mFailedNum < Constants.MAX_RETRIES) {
            return Downloads.Impl.STATUS_WAITING_FOR_NETWORK; // 无网络导致下载结束，子线程无需重试
        } else {
            logWarning(" reached max retries for " + mInfo.mId);
            return Downloads.Impl.STATUS_HTTP_DATA_ERROR;
        }
    }

    /**
     * Open a stream for the HTTP response entity, handling I/O errors.
     *
     * @return an InputStream to read the response entity
     */
    private InputStream openResponseEntity(HttpResponse response)
            throws StopRequestException {
        try {
            return response.getEntity().getContent();
        } catch (IOException ex) {
            logError(" openResponseEntity IOException", ex);
            throw new StopRequestException(getFinalStatusForHttpError(),
                    "while getting entity: " + ex.toString(), ex);
        }
    }

    /**
     * Transfer as much data as possible from the HTTP response to the destination file.
     *
     * @param data         buffer to use to read data
     * @param entityStream stream for reading the HTTP response entity
     */
    private void transferData(ChildDownloadInfo childInfo, byte[] data, InputStream entityStream)
            throws StopRequestException {
        logInfo(" transferData, childInfo = " + childInfo.toString());
        for (; ; ) {
            int bytesRead = readFromResponse(data, entityStream);
            if (bytesRead == -1) { // success, end of stream already reached
                handleEndOfStream(childInfo);
                childInfo.mCompleted = true;
                return;
            }

            writeDataToDestination(childInfo, data, bytesRead);
            childInfo.mCurrentBytes += bytesRead;
            reportProgress(childInfo);
            checkPausedOrCanceled();
        }

    }

    /**
     * Read some data from the HTTP response stream, handling I/O errors.
     *
     * @param data         buffer to use to read data
     * @param entityStream stream for reading the HTTP response entity
     * @return the number of bytes actually read or -1 if the end of the stream has been reached
     */
    private int readFromResponse(byte[] data, InputStream entityStream)
            throws StopRequestException {
        try {
            return entityStream.read(data);
        } catch (IOException ex) {
            throw new StopRequestException(getFinalStatusForHttpError(),
                    "while reading response: " + ex.toString(), ex);
        }
    }

    /**
     * Called when we've reached the end of the HTTP response stream, to update the database and
     * check for consistency.
     */
    private void handleEndOfStream(ChildDownloadInfo childInfo) throws StopRequestException {
        childInfo.mSpeed = 0;
        boolean lengthMismatched = (childInfo.mCurrentBytes != childInfo.mTotalBytes);
        if (lengthMismatched) {
            throw new StopRequestException(getFinalStatusForHttpError(), "closed socket before end of file");
        } else {
            syncDataToMain(ChildDownloadManager.DOWNLOAD_PROGRESS_MESSAGE, childInfo);
        }
    }

    /**
     * Write a data buffer to the destination file.
     *
     * @param data      buffer containing the data to write
     * @param bytesRead how many bytes to write from the buffer
     */
    private void writeDataToDestination(ChildDownloadInfo childInfo, byte[] data, int bytesRead)
            throws StopRequestException {
        for (; ; ) {
            try {
                if (childInfo.mOutStream == null) {
                    childInfo.mOutStream = new FileOutputStream(childInfo.mBlockName, true);
                }
                childInfo.mOutStream.write(data, 0, bytesRead);
                return;
            } catch (IOException ex) {
                throw new StopRequestException(Downloads.Impl.STATUS_FILE_ERROR, "Failed to write child file", ex);

            } finally {
                closeDestination(childInfo);
            }
        }
    }

    /**
     * Report download progress through the database if necessary.
     */
    private void reportProgress(ChildDownloadInfo childInfo) {
        final long now = SystemClock.elapsedRealtime();
        final long sampleDelta = now - childInfo.mSpeedSampleStart;
        if (sampleDelta > DEFAULT_SAMPLE_TIME) {
            final long sampleSpeed = ((childInfo.mCurrentBytes - childInfo.mSpeedSampleBytes) * 1000) / sampleDelta;

            if (childInfo.mSpeed == 0) {
                childInfo.mSpeed = sampleSpeed;
            } else {
                childInfo.mSpeed = ((childInfo.mSpeed * 3) + sampleSpeed) / 4;
            }

            childInfo.mSpeedSampleStart = now;
            childInfo.mSpeedSampleBytes = childInfo.mCurrentBytes;

            // 回调下载进度，每600ms进行一次
            syncDataToMain(ChildDownloadManager.DOWNLOAD_PROGRESS_MESSAGE, childInfo);
        }

        if (childInfo.mCurrentBytes - childInfo.mBytesNotified > Constants.MIN_PROGRESS_STEP
                && now - childInfo.mTimeLastNotification > Constants.MIN_PROGRESS_TIME) {
            childInfo.mBytesNotified = childInfo.mCurrentBytes;
            childInfo.mTimeLastNotification = now;
        }

    }

    /**
     * Check if the download has been paused or canceled, stopping the request appropriately if it
     * has been.
     */
    private void checkPausedOrCanceled() throws StopRequestException {
        synchronized (mInfo) {
            if (mInfo.mControl == Downloads.Impl.CONTROL_PAUSED) {
                logDebug(" checkPausedOrCanceled CONTROL_PAUSED");
                throw new StopRequestException(Downloads.Impl.STATUS_PAUSED_BY_APP, "download paused by owner");
            }
            if (mInfo.mStatus == Downloads.Impl.STATUS_CANCELED) {
                logDebug(" checkPausedOrCanceled STATUS_CANCELED");
                throw new StopRequestException(Downloads.Impl.STATUS_CANCELED, "download canceled");
            }
        }
    }

    /**
     * Close the destination output stream.
     */
    private void closeDestination(ChildDownloadInfo childInfo) {
        try {
            // close the file
            if (childInfo.mOutStream != null) {
                childInfo.mOutStream.close();
                childInfo.mOutStream = null;
            }
        } catch (IOException ex) {
            logError(" exception when closing the file after download : ", ex);
            // nothing can really be done if the file can't be closed
        }
    }

    private void handleDownFailed(int status, Exception e) {
        logDebug(" handleDownFailed() mChildInfo: " + mChildInfo + " Exception: " + e.toString());
        mChildInfo.mSpeed = 0;
//        mChildInfo.mCurrentBytes = 0;
        mChildInfo.mStatus = status;
        mChildInfo.mException = e;
        mChildInfo.mErrorMsg = e.getMessage();
        syncDataToMain(ChildDownloadManager.DOWNLOAD_FAILED_MESSAGE, mChildInfo);
    }

    private void clearTmpFile(String filePath) {
        logDebug(" clear tmp file in child, mInfo.mStatus: " + mInfo.mStatus);
        if (!TextUtils.isEmpty(filePath)) {
            File file = new File(filePath);
            if (file.exists()) {
                file.delete();
            }
        }
    }

    // 将子下载数据同步到主线程
    private void syncDataToMain(int type, ChildDownloadInfo childInfo) {
        Message message = mHandler.obtainMessage();
        message.what = type;
        message.obj = childInfo;
        mHandler.sendMessage(message);
    }

    private void logDebug(String msg) {
        VLog.d(TAG, "[" + mInfo.mId + "](" + mChildInfo.mTid + ") " + msg);
    }

    private void logWarning(String msg) {
        VLog.w(TAG, "[" + mInfo.mId + "](" + mChildInfo.mTid + ") " + msg);
    }

    private void logInfo(String msg) {
        VLog.i(TAG, "[" + mInfo.mId + "](" + mChildInfo.mTid + ") " + msg);
    }

    private void logError(String msg, Throwable t) {
        VLog.e(TAG, "[" + mInfo.mId + "](" + mChildInfo.mTid + ") " + msg, t);
    }

}
